﻿using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.IO;

namespace DataCenter
{
    public class DataCenter
    {
        public static string[] token
        {
            get => _token;
            set
            {
                if (value.Length == 2)
                {
                    _token[0] = value[0];
                    _token[1] = value[1];
                }
            }
        }
        static string[] _token = new string[2];
        public static Models.User user { get; private set; }


        static HttpClient client = new HttpClient();

        public static async Task<int> GetStockNumInBK(string BKName)
        {
            string url = string.Format(MyConst.URL.GET_STOCKNUM_IN_BK, BKName);
            string r = await GetRemoteData(url);
            return int.Parse(Regex.Match(r, @"\d+").Value);
        }

        public static async Task<Models.User> Login(string user_id, string passwd)
        {
            Dictionary<string, string> d = new Dictionary<string, string>();
            d["user_id"] = user_id;
            d["passwd"] = Utility.EncipherPWD(passwd);
            var str = await GetRemoteData(MyConst.URL.LOGIN, JsonConvert.SerializeObject(d));
            var u = Precook(str)["user"];
            Models.User user = new Models.User(user_id, (string)u["nickname"], (int)u["status"]);
            DataCenter.user = user;
            return user;
        }

        public static async Task<bool> Register(string user_id,string nickname, string passwd, string yzm)
        {
            Dictionary<string, string> d = new Dictionary<string, string>();
            d["user_id"] = user_id;
            d["nickname"] = nickname;
            d["passwd"] = Utility.EncipherPWD(passwd);
            d["yzm"] = yzm;
            var str = await GetRemoteData(MyConst.URL.REGISTER, JsonConvert.SerializeObject(d));
            Console.WriteLine(str);
            Precook(str);
            return true;
        }

        public static async Task<bool> ResetPasswd(string user_id,string passwd, string yzm)
        {
            Dictionary<string, string> d = new Dictionary<string, string>();
            d["user_id"] = user_id;
            d["passwd"] = Utility.EncipherPWD(passwd);
            d["yzm"] = yzm;
            var str = await GetRemoteData(MyConst.URL.RESETPASSWD, JsonConvert.SerializeObject(d));
            Console.WriteLine(str);
            Precook(str);
            return true;
        }

        public static async Task<Models.User> TopUp(string user_id, string cardKey,string yzm, string friend_id)
        {
            Dictionary<string, string> d = new Dictionary<string, string>();
            d["user_id"] = user_id;
            d["card_key"] = cardKey;
            d["yzm"] = yzm;
            d["friend_id"] = friend_id;
            var str = await GetRemoteData(MyConst.URL.TOPUP, JsonConvert.SerializeObject(d));
            Console.WriteLine(str);
            var r = Precook(str);
            if (r != null)
            {
                var u = r["user"];
                Models.User user = new Models.User(user_id, (string)u["nickname"], (int)u["status"]);
                DataCenter.user = user;
                return user;
            }
            return null;
        }
        public static string TopupDirect(int amount, string userId,string friendId)
        {
            Dictionary<string, object> d = new Dictionary<string, object>();
            d["token"] = DataCenter.token;
            d["amount"] = amount;
            d["user_id"] = userId;
            d["friend_id"] = friendId;
            var str = GetRemoteDataSync(MyConst.URL.TOPUPDIRECT, JsonConvert.SerializeObject(d));
            Console.WriteLine(str);
            return str;
        }



        public static async Task<string> GetUserPoint()
        {
            var req = new Request();
            var str = await RequestMyApi(MyConst.URL.GETUSERPOINT, req);
            Console.WriteLine(str);
            return str;
        }

        public static string CreateCards(int faceValue, int num)
        {
            Dictionary<string, object> d = new Dictionary<string, object>();
            d["token"] = DataCenter.token;
            d["face_amount"] = faceValue;
            d["num"] = num;
            var str = GetRemoteDataSync(MyConst.URL.CREATECARDS, JsonConvert.SerializeObject(d));
            Console.WriteLine(str);
            var jo = Precook(str);
            return jo.ToString();
        }


        public static async void SendYZM(string phone)
        {
            Dictionary<string, object> d = new Dictionary<string, object>();
            d["phone"] = phone;
            var str = await GetRemoteData(MyConst.URL.GETYZM, JsonConvert.SerializeObject(d));
            Console.WriteLine(str);
        }




        public static async Task<string[]> GetStockListOfBK(string bkName, int page, Enums.SortFlag flag, bool asc = false, int numOfPage = 20)
        {
            string url = string.Format(MyConst.URL.GET_STOCKLIST_OF_BK, page, numOfPage, flag, TryChangeType(asc, typeof(int)), bkName);
            string res = await GetRemoteData(url);
            res = res.Substring(2, res.Length - 4);
            var l = Regex.Split(res, "},{");
            string[] r = new string[l.Length];
            for (int i = 0; i < l.Length; i++)
            {
                r[i] = l[i].Substring(8, 8);
            }

            return r;
        }

        public static Models.SearchItem[] Search(string k)
        {
            var req = new Request("search", new { key = k });
            return RequestMyApiSync<Models.SearchItem>(req);
        }

        public static int bkTotal = 0;
        public static async Task<Models.BKRanking[]> GetBKRanking(Enums.BKType bk, int page, int desc = 0)
        {
            var req = new Request("bk_ranking", new {bk_name = bk.ToString(), page = page, num = 20, desc = desc });
            string text = await GetRemoteData(MyConst.URL.CALL_API, JsonConvert.SerializeObject(req));
            JToken jo = Precook(text);
            bkTotal = (int)jo["total"];
            return ChangeModel<Models.BKRanking>(jo);
        }


        public static async Task<Models.StockInfo[]> GetSSSJ(string[] stockList)
        {
            if (stockList == null)
                throw new ArgumentNullException();

            string stocksStr = string.Join(",", stockList);
            var url = string.Format(MyConst.URL.SSSJ, stocksStr);
            string rStr = await GetRemoteData(url);
            if (rStr.Length < 100)
                throw new MyException.NullValueException("服务器返回：" + rStr + "\r\nurl：" + url);

            string[] columns = new string[] { "", "name", "", "trade", "preclose", "open", "vol", "outvol", "invol", "buy1", "buy1vol", "buy2", "buy2vol", "buy3", "buy3vol", "buy4", "buy4vol", "buy5", "buy5vol", "sell1", "sell1vol", "sell2", "sell2vol", "sell3", "sell3vol", "sell4", "sell4vol", "sell5", "sell5vol", "", "", "change", "changepercent", "high", "low", "", "", "amount", "turnover", "ttm", "", "", "", "amplitude", "circulatingAmount", "totalAmount", "pb", "zt", "dt", "qrr", "", "average", "pe", "spe" };
            List<string> symbols = new List<string>();
            List<object> data = new List<object>();
            Dictionary<string, object> d = new Dictionary<string, object>();
            d["columns"] = columns;
            d["index"] = symbols;
            d["data"] = data;

            //取出symbol
            var rs = Regex.Matches(rStr, @"(?<=v_).+?(?==)");
            foreach (Match i in rs)
            {
                symbols.Add(i.Value);
            }
            //匹配数据
            var ds = Regex.Matches(rStr, "(?<==\").+?(?=\";)");
            foreach (Match i in ds)
            {
                data.Add(i.Value.Split('~'));
            }
            
            var sInfos = ChangeModel<Models.StockInfo>(JObject.FromObject(d));
            return sInfos;
        }

        //public static async Task<Models.StockInfo[]> GetStockInfos(string[] stockList)
        //{
        //    if (stockList == null)
        //        throw new ArgumentNullException();

        //    string stocksStr = string.Join(",", stockList);
        //    var req = new Request("sssj", new { symbols_str = stocksStr });
        //    var sInfos = await RequestMyApi<Models.StockInfo>(req);
        //    return sInfos;
        //}

        public static async Task<Models.Kline[]> GetKline(string symbol, string freq = "day", string adj = "qfq", string startTime = null, string endTime = null)
        {
            if (startTime == null && endTime != null)
                throw new MyException.NoStartTimeError("There is no StartTime");

            Models.Kline[] ks;
            if (startTime == null)
            {
                var req = new Request(MyConst.APINames.LASTK, new { symbol = symbol, freq = freq, num = 100, adj = adj });
                ks = await RequestMyApi<Models.Kline>(req);
            }
            else
            {
                if (endTime == null)
                    endTime = DateTime.Now.ToString("yyyy-MM-dd");
                List<Models.Kline> buffer = new List<Models.Kline>();
                while (true)
                {
                    var req = new Request(MyConst.APINames.GETK, new { symbol = symbol, freq = freq, adj = adj, start = startTime, end = endTime });
                    ks = await RequestMyApi<Models.Kline>(req);
                    buffer.AddRange(ks);
                    if (ks.Length < 320)
                        break;
                    endTime = ks[0].time.AddDays(-1).ToString("yyyy-MM-dd");
                }

                ks = buffer.ToArray();
            }

            return ks;
        }

        public static async Task<Models.Tick[]> GetTickLine(string symbol)
        {
            var req = new Request(MyConst.APINames.TICK, new { symbol = symbol });
            return await RequestMyApi<Models.Tick>(req);
        }

        public static async Task<Models.zhishuYL[]> GetZhishuYL()
        {
            var req = new Request(MyConst.APINames.ZHISHUYL, new { });
            return await RequestMyApi<Models.zhishuYL>(req);
        }

        public static async Task<Dictionary<string, object>> GGYL(string fls)
        {
            var req = new Request(MyConst.APINames.GGYL, new { keys = fls });
            string text = await GetRemoteData(MyConst.URL.CALL_API, JsonConvert.SerializeObject(req));
            JObject jo = (JObject)Precook(text);
            Dictionary<string, object> d = new Dictionary<string, object>();
            foreach (string fl in fls.Split(','))
            {
                JToken j;
                if (jo.TryGetValue(fl, out j))
                {
                    d[fl] = DetermineFL(fl, (JObject)j);
                }
            }
            return d;
        }

        static object DetermineFL(string fl, JObject jo)
        {
            switch (fl)
            {
                case "zftop10":
                case "dftop10":
                case "zf5day":
                case "df5day":
                    return ChangeModel<Models.ZDB>(jo);
                case "zllr":
                case "zllc":
                    return ChangeModel<Models.ZLLR>(jo);
                case "hstop10":
                case "hs5day":
                case "ddpx_buy":
                case "ddpx_sell":
                    return ChangeModel<Models.HSTop>(jo);
                case "cjetop10":
                    return ChangeModel<Models.CJETop>(jo);
            }

            return null;
        }


        public static JToken GetNewsList(int page, string symbol = null)
        {
            Request req;
            if (symbol != null)
                req = new Request(MyConst.APINames.NEWSLIST, new { symbol = symbol, page = page });
            else
                req = new Request("get_news_list", new { page = page });

            var text = GetRemoteDataSync(MyConst.URL.CALL_API, JsonConvert.SerializeObject(req));
            return Precook(text);
        }

        //参数传递列表中获取的id和url
        public async static Task<string> GetNewsContent(string id, string url)
        {
            string contentURL = string.Format(MyConst.URL.NEWS_CONTENT, id);
            client.DefaultRequestHeaders.Add("Referer", url);
            string res = await GetRemoteData(contentURL);
            client.DefaultRequestHeaders.Remove("Referer");
            JObject j = JObject.Parse(res);
            if ((int)j["code"] == 0)
                return (string)j["data"]["data"]["content"]["text"];
            return "null";
        }


        //获取个股基本信息
        public static JToken GetStockGeneralInfomation(string symbol)
        {
            var req = new Request(MyConst.APINames.GENERALINFORMATION, new { symbol = symbol });
            var text = GetRemoteDataSync(MyConst.URL.CALL_API, JsonConvert.SerializeObject(req));
            var r = Precook(text);
            return r;
        }

        public static bool HavePermission(string apiName)
        {
            if (user.status > -1)
                return true;
            Dictionary<string, string> d = new Dictionary<string, string>();
            d["api_name"] = apiName; ;
            var str = GetRemoteDataSync(MyConst.URL.HAVEPERMISSION, JsonConvert.SerializeObject(d));
            int p = int.Parse(str);
            if (user.status >= p)
                return true;
            return false;
        }


        //=============================================


        private static async Task<T[]> RequestMyApi<T>(Request req)
        {
            string text = await GetRemoteData(MyConst.URL.CALL_API, JsonConvert.SerializeObject(req));
            JToken jo = Precook(text);
            return ChangeModel<T>(jo);
        }
        private static T[] RequestMyApiSync<T>(Request req)
        {
            string text = GetRemoteDataSync(MyConst.URL.CALL_API, JsonConvert.SerializeObject(req));
            JToken jo = Precook(text);
            return ChangeModel<T>(jo);
        }

        private static async Task<string> RequestMyApi(string url, Request req)
        {
            string text = await GetRemoteData(url, JsonConvert.SerializeObject(req));
            JToken jo = Precook(text);
            return jo.ToString();
        }

        static T[] ChangeModel<T>(JToken jo)
        {
            Type t = typeof(T);

            //建立字段与数据列的映射关系
            PropertyInfo indexP = GetIndexProperty(t);
            Dictionary<int, PropertyInfo> columnMap = new Dictionary<int, PropertyInfo>();
            List<string> columnList = jo["columns"].Select(c => (string)c).ToList();
            for (int i = 0; i < columnList.Count; i++)
            {
                if (columnList[i] == "")
                    continue;

                PropertyInfo pInfo = t.GetProperty(columnList[i], BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
                if (pInfo == null)
                {
                    Console.WriteLine("在" + t + "中没有找到名为" + columnList[i] + "的属性");
                    continue;
                }
                columnMap[i] = pInfo;
                //columnMap[i] = pInfo ?? throw new Exception("在" + t + "中没有找到名为" + columnList[i] + "的属性");
            }

            List<JToken> dataList = jo["data"].Select(d =>
            {
                if ((string)((JArray)d)[0] != "")
                    return d; 
                return null;

            }).ToList();
            List<T> res = new List<T>();
            for (int i = 0; i < dataList.Count; i++)
            {
                //创建实例，填充数据
                if (dataList[i] != null)
                {
                    T model = (T)Activator.CreateInstance(t);
                    if (indexP != null)
                        indexP.GetSetMethod(true).Invoke(model, new object[] { TryChangeType(jo["index"][i], indexP.PropertyType) });
                    foreach (var kv in columnMap)
                    {
                        if (dataList[i][kv.Key].ToString() != "")
                        {
                            kv.Value.GetSetMethod(true).Invoke(model, new object[] { TryChangeType(dataList[i][kv.Key], kv.Value.PropertyType) });
                        }
                    }

                    res.Add(model);
                }
            }

            return res.ToArray();
        }

        private static async Task<string> GetRemoteData(string url, string dataStr = null)
        {
            string res = string.Empty;
            if (dataStr == null)
            {
                res = await client.GetStringAsync(url);
                if (res == "null")
                    throw new MyException.NullValueException("服务器返回 null。请求地址：" + url);
            }
            else
            {
                var content = new StringContent(JsonConvert.SerializeObject(dataStr));
                content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
                content.Headers.ContentType.CharSet = "utf-8";
                var resp = await client.PostAsync(url, content);
                //string result = string.Empty;
                if (!resp.IsSuccessStatusCode)
                    throw new MyException.NullValueException("服务器返回不成功。请求地址：" + url);
                res = await resp.Content.ReadAsStringAsync();
            }

            return res;
        }

        private static string GetRemoteDataSync(string url, string dataStr = null)
        {
            string res = string.Empty;
            if (dataStr == null)
            {
                res = client.GetStringAsync(url).Result;
                if (res == "null")
                    throw new MyException.NullValueException("服务器返回 null。请求地址：" + url);
            }
            else
            {
                var content = new StringContent(JsonConvert.SerializeObject(dataStr));
                content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
                content.Headers.ContentType.CharSet = "utf-8";
                var resp = client.PostAsync(url, content).Result;
                //string result = string.Empty;
                if (!resp.IsSuccessStatusCode)
                    throw new MyException.NullValueException("服务器返回不成功。请求地址：" + url);
                res = resp.Content.ReadAsStringAsync().Result;
            }

            return res;
        }

        private static PropertyInfo GetIndexProperty(Type t)
        {
            FieldInfo f = t.GetField("indexName", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly);
            if (f == null)
                return null;
            string name = (string)f.GetValue(null);
            PropertyInfo pinfo = t.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) ?? throw new Exception("没有在" + t.ToString() + "中找到名为" + name + "的属性");
            return pinfo;
        }

        private static object TryChangeType(object obj, Type t, object defaultValue = null)
        {
            if (obj == null || obj == DBNull.Value)
                return defaultValue;

            if (obj.GetType() == t)
                return obj;

            if (t == typeof(DateTime))
            {
                try
                {
                    return TextToTime(obj.ToString());
                }
                catch (Exception e)
                {
                    Console.WriteLine(obj.ToString());
                    throw e;
                }

            }

            var tryParse = t.GetMethod("TryParse", BindingFlags.Public | BindingFlags.Static, Type.DefaultBinder, new Type[] { obj.GetType(), t.MakeByRefType() }, new ParameterModifier[] { new ParameterModifier(2) });

            if (tryParse != null)
            {
                var parameters = new object[] { obj, Activator.CreateInstance(t) };
                bool success = (bool)tryParse.Invoke(null, parameters);
                if (success)
                    return parameters[1];
                else
                {
                    if (defaultValue != null)
                        return defaultValue;
                    return parameters[1];
                }
            }

            return Convert.ChangeType(obj, t);
        }

        private static DateTime TextToTime(string timeStr)
        {
            string[] formats = { "yyyyMMddHHmm", "yyyy-MM-dd" };
            DateTime dt;
            if (DateTime.TryParseExact(timeStr, formats, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out dt))
                return dt;

            //timestamp
            if (timeStr.Length >= 10)
                timeStr = timeStr.Substring(0, 10);
            long ts = Convert.ToInt64(timeStr);
            System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));
            return startTime.AddSeconds(ts);
        }

        /// <summary>
        /// 在SorceModel中查找与T具有相同属性名的值并赋给T中对应的属性。
        /// </summary>
        /// <param name="sourceModel">包含所需值的对象实例</param>
        /// <returns></returns>
        private static void FillData(object sourceModel, object targetModel)
        {
            var tT = targetModel.GetType();
            var tps = tT.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            var oT = sourceModel.GetType();

            foreach (var p in tps)
            {
                var op = oT.GetProperty(p.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
                if (op != null)
                {
                    p.GetSetMethod(true).Invoke(targetModel, new object[] { TryChangeType(op.GetGetMethod().Invoke(sourceModel, null), p.PropertyType) });
                }
            }
        }

        static JToken Precook(string text)
        {
            var jo = JObject.Parse(text);
            if ((int)jo["code"] == 0)
            {
                if (jo["data"].ToString() != "{}")
                {
                    token[0] = (string)jo["data"]["token"][0];
                    token[1] = (string)jo["data"]["token"][1];
                    return jo["data"]["data"];
                }
                return null;
            }
            int code = (int)jo["code"];
            string msg = (string)jo["msg"];
            if (code == 1001)
                throw new MyException.APINoPermissionException();
            throw new MyException.APIReturnException(code + "~" + msg);
        }





        class Request
        {
            public string[] token { get => DataCenter.token; }
            public object api { get; private set; }

            public Request(string apiName, object apiArgs)
            {
                api = new { api_name = apiName, args = apiArgs };
            }

            public Request()
            {
            }
        }
    }
}
